Add static abstract Info and obsolete QuantityInfo instance member on interfaces#1675
Conversation
Code ReviewOverviewThis PR adds static abstract The design is sound: the instance property always returns a per-type static value, so exposing it as a static abstract eliminates boxing and virtual dispatch. Breaking ChangesSoft break (intentional): Potential hard break for custom quantities: Consider a note in the XML docs for Code QualityPositives:
Suggestions:
Test CoverageThe four new tests in
Nice-to-have: a test covering the throw behaviour of Minor NitThe diff removes the UTF-8 BOM from line 1 of SummaryOverall a well-designed change with a clear rationale and solid backward-compatibility strategy. The main concern worth addressing before merge is explicitly documenting (and testing) that |
28e69a4 to
d089592
Compare
Code ReviewOverviewThis PR adds Breaking ChangesObsolete Marking Custom quantity authors The Potential Issues
The extension method routes through /// <remarks>
/// This method throws if the quantity's runtime type is not registered in
/// <see cref="UnitsNetSetup.Default"/>. Custom quantities must be registered
/// first via <see cref="UnitsNetSetup"/> or use the instance <c>QuantityInfo</c>
/// property directly.
/// </remarks>Typed cast can silently mismatch public static QuantityInfo<TUnit> GetQuantityInfo<TUnit>(this IQuantity<TUnit> quantity)
=> (QuantityInfo<TUnit>)UnitsNetSetup.Default.Quantities.GetQuantityInfo(quantity.GetType());If the registered Benchmark now measures dictionary lookup In Test CoverageThe four new tests in
Minor / Style
SummaryThe overall direction is correct and the implementation is careful. The main things to address before merging:
|
Code Review — PR #1675: Add static abstract Info and obsolete QuantityInfo instance member on interfacesOverviewThis PR introduces Potential Issues1. Behavioral regression for unregistered custom quantities (medium severity)
// QuantityExtensions.cs — was: quantity.QuantityInfo[quantity.UnitKey]
return quantity.GetQuantityInfo()[quantity.UnitKey]; // throws for unregistered custom quantitiesThe PR correctly suppresses the obsolete warning with an explanatory comment in serialization/debug proxy code for exactly this reason, but the same consideration wasn't applied to For the generic overload #if NET
return TQuantity.Info[quantity.Unit];
#else
return quantity.QuantityInfo[quantity.Unit];
#endif2. Build-breaking for callers with The new 3. Benchmark now measures registry lookup overhead // QuantityConversionBenchmarks.cs
foreach (UnitInfo unitInfo in quantity.GetQuantityInfo().UnitInfos) // was: quantity.QuantityInfo.UnitInfosThis adds a dictionary lookup to what was previously a pure conversion benchmark, changing what's being measured. Either revert this file or add a separate benchmark that isolates the two costs. Strengths
Minor
SummaryThe design is well thought-out and the implementation is mostly correct. The main asks before merging:
|
|
Code Review: Add static abstract Info and obsolete QuantityInfo instance member on interfaces Summary: This PR adds static abstract Info to IQuantityOfType and IQuantity, marks the instance QuantityInfo property Obsolete on .NET 5+, and introduces GetQuantityInfo() extension methods as the new discoverable API. The design is clean and well-reasoned. Breaking Changes
Performance
Code Quality
Tests The four new tests cover non-generic extension, typed extension, and both static abstract paths. Assert.Same confirms singleton behavior. Missing: a test for calling GetQuantityInfo() on an unregistered custom quantity to document the expected failure. Minor QuantityExtensions.As(UnitSystem) diverges between NET (uses GetQuantityInfo, fails for unregistered) and netstandard2.0 (uses instance property, always works). A comment explaining the asymmetry would help. HowMuch.Info field-to-property change is correct and necessary. Verdict The design is sound and the implementation is careful. Main asks before merging:
|
Per discussion on #1657, this introduces a static abstract `Info` member on the IQuantityOfType<TQuantity> and IQuantity<TSelf,TUnitType> interfaces under #if NET, marks the existing instance QuantityInfo property as [Obsolete] on .NET 5+, and adds GetQuantityInfo() extension methods on QuantityExtensions so callers have a single discoverable API that works on every TFM. Why - The instance QuantityInfo property invariably returns a per-type static value. Exposing it as an instance member implies it can vary per instance, which it cannot, and incurs interface dispatch (boxing on structs) for every call. - The static abstract member lets generic algorithms reach the info with `TSelf.Info` directly, no boxing, no virtual call. - The extension method pair (`GetQuantityInfo()` / `GetQuantityInfo<TUnit>()`) is the discoverable replacement for callers that only have an `IQuantity` reference. It looks the quantity up via `UnitsNetSetup.Default.Quantities`. - Keeping the instance property obsolete (warning) instead of removing it preserves source compatibility for existing callers and the netstandard2.0 contract. We can promote to error / remove once netstandard2.0 is dropped. Implementation notes - Generated quantities already expose `public static QuantityInfo<TSelf, TUnitType> Info { get; }`, which directly satisfies the typed static abstract. The non-generic `IQuantityOfType<TSelf>.Info` is satisfied by a default static implementation in IQuantity<TSelf,TUnitType>: `static QuantityInfo IQuantityOfType<TSelf>.Info => TSelf.Info;`. No codegen change required. - The IQuantity bridge `QuantityInfo IQuantity.QuantityInfo => QuantityInfo;` chain inside the interfaces uses #pragma to suppress the obsolete warning on the bridge itself. - Internal callers in UnitsNet were migrated to either `TSelf.Info` / `TSelf.From` (where the generic constraint allows) or `quantity.GetQuantityInfo()` (where it doesn't). Callers that must keep working for custom quantities not registered in `UnitsNetSetup.Default` (JsonNet serialization, debugger proxy, QuantityTypeConverter) keep using the instance member with a `#pragma warning disable CS0618` and a comment explaining why. - HowMuch test custom quantity changed `public static readonly` field to `public static QuantityInfo Info { get; }` property to satisfy the static abstract. - Tests added for round-trip equivalence between `Mass.Info`, `mass.GetQuantityInfo()`, `TQuantity.Info` (via static abstract on IQuantityOfType<T>), and `TSelf.Info` (via static abstract on IQuantity<TSelf,TUnit>). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code Review -- PR #1675: Static abstract
|
d089592 to
7abde96
Compare
Code ReviewOverviewThis PR introduces The design rationale is solid: the instance property returns a per-type static value through interface dispatch, including boxing on structs. The default static bridge Breaking ChangesSoft break (obsolete warning): Custom quantity implementors -- field to property: The // Before (field -- does not satisfy static abstract)
public static readonly QuantityInfo<HowMuch, HowMuchUnit> Info = new(...);
// After (property -- satisfies static abstract)
public static QuantityInfo<HowMuch, HowMuchUnit> Info { get; } = new(...);This is a compile-time break for external custom quantities. The migration guide / release notes should call this out explicitly. Potential Issues1. public static QuantityInfo GetQuantityInfo(this IQuantity quantity)
=> UnitsNetSetup.Default.Quantities.GetQuantityInfo(quantity.GetType());The PR correctly keeps A short 2. Typed overload unchecked cast public static QuantityInfo<TUnit> GetQuantityInfo<TUnit>(this IQuantity<TUnit> quantity)
=> (QuantityInfo<TUnit>)UnitsNetSetup.Default.Quantities.GetQuantityInfo(quantity.GetType());A mismatch produces an Code Quality -- Positives
Test CoverageThe four new tests in
One gap worth adding: a test for Minor NitThe diff removes the UTF-8 BOM from the first line of SummaryWell-designed change with a clear rationale and a sensible backward-compatibility strategy. Main items to address before merging:
|
Per discussion on #1657, this introduces a static abstract
Infomember on the IQuantityOfType and IQuantity<TSelf,TUnitType> interfaces under #if NET, marks the existing instance QuantityInfo property as [Obsolete] on .NET 5+, and adds GetQuantityInfo() extension methods on QuantityExtensions so callers have a single discoverable API that works on every TFM.Why
TSelf.Infodirectly, no boxing, no virtual call.GetQuantityInfo()/GetQuantityInfo<TUnit>()) is the discoverable replacement for callers that only have anIQuantityreference. It looks the quantity up viaUnitsNetSetup.Default.Quantities.Implementation notes
public static QuantityInfo<TSelf, TUnitType> Info { get; }, which directly satisfies the typed static abstract. The non-genericIQuantityOfType<TSelf>.Infois satisfied by a default static implementation in IQuantity<TSelf,TUnitType>:static QuantityInfo IQuantityOfType<TSelf>.Info => TSelf.Info;. No codegen change required.QuantityInfo IQuantity.QuantityInfo => QuantityInfo;chain inside the interfaces uses #pragma to suppress the obsolete warning on the bridge itself.TSelf.Info/TSelf.From(where the generic constraint allows) orquantity.GetQuantityInfo()(where it doesn't). Callers that must keep working for custom quantities not registered inUnitsNetSetup.Default(JsonNet serialization, debugger proxy, QuantityTypeConverter) keep using the instance member with a#pragma warning disable CS0618and a comment explaining why.public static readonlyfield topublic static QuantityInfo Info { get; }property to satisfy the static abstract.Mass.Info,mass.GetQuantityInfo(),TQuantity.Info(via static abstract on IQuantityOfType), andTSelf.Info(via static abstract on IQuantity<TSelf,TUnit>).